<?php
/**
 * Create On 2015-4-22
 * Author By YuLin
 * Email  By dwzhanglong@126.com
 * Func	  
 */

class Ytemplate
{
    //  报错调试
    var $debug = true;
    
    //  左标签符号
    var $leftTag = '{';
    
    //  右标签符号
    var $rightTag = '}';
    
    //  模板扩展名
    var $templateExt = 'php';
    
    //  模板路径
    var $templateDir = 'template';
    
    //  编译路径
    var $templateCompile = 'compile';
    
    //  引擎变量
    var $data = array();
	
	//	模版中是否允许插入PHP
	var $phpOff = true;
	
	/* 模板编译信息提示 */
	var $_error = array(
		' The template file is not exist',
		' The template file is too large',
		' The template file is loaded error',
		' Bad error',
		' Program warning',
		' Fatal error',
		' File name',
		' Error level',
		' Error in',
		' Error information',
		' Cache file path',
		' Error happen in line',
		' Line'
	);
    
    function __construct($config = array())
    {
        isset($config['debug']) && $this->debug = $config['debug'];
        isset($config['phpOff']) && $this->phpOff = $config['phpOff'];
        $config['leftTag'] && $this->leftTag = $config['leftTag'];
        $config['rightTag'] && $this->rightTag = $config['rightTag'];
        $config['templateExt'] && $this->templateExt = $config['templateExt'];
        $config['templateDir'] && $this->templateDir = $config['templateDir'];
        $config['templateCompile'] && $this->templateCompile = $config['templateCompile'];
    }
    
	/* 向模板引擎中注入变量 */
	function assign($key,$val = null)
	{
		if (!$key)
        {
            return ;
        }
        
		if (is_array($key))
		{
			foreach ($key as $k=>$v)
			{
				$this->data[$k] = $v;
			}
		}
        else
        {
			$this->daa[$key] = $val;	
		}
	}
	
	/* 取得变量值 */
	function & GetValue($key)
	{
		if (isset($this->data[$key]))
		{
			return $this->data[$key];
		}
        else
        {
			global $$key;
			if ($$key)
			{
				$this->assign($key,$$key);
			}
			return $$key;
		}
	}
    
    /*  模板路径 */
    function GetTemplatePath($tplname)
    {
        return $this->templateDir.'/'.$tplname.'.'.$this->templateExt;
    }
    
    /*  模板编译路径 */
    function GetTemplateCompilePath($tplname)
    {
        return $this->templateCompile.'/'.md5($this->GetTemplatePath($tplname)).'.php';
    }
	
	/* 读取文件 */
	function ReadTemplate($filename)
	{
		return file_get_contents($filename);
	}
	
	/* 写入文件 */
	private function WriteTemplate($filename,$String)
	{
		/* 调用递归创建目录  */
		$this->CreateDir(dirname($filename));

		/* 以写入方式打开文件句柄,开启 flock  */
		$fopen = fopen($filename,'w');
		flock($fopen, LOCK_EX + LOCK_NB);
		$fwrite = fwrite($fopen,$String);
		/* 失败重新尝试写入  */
		if (!$fwrite) $fwrite = fwrite($fopen,$String);
		flock($fopen, LOCK_UN + LOCK_NB);
		fclose($fopen);

		return $fwrite;
	}
	
	/* 循环创建目录 */
	function CreateDir($Dir)
	{
        if(is_dir($Dir))
        {
            return true;
        }
        
        if(@mkdir($Dir,'0777',true))
        {
            return true;
        }
        
        return false;
	}
	
	/* 循环删除目录 */
	function RmDir($dir,$to = false)
	{
		if ($list = glob($dir.'/*'))
		{
			foreach ($list as $file)
			{
				is_dir($file) ? $this->RmDir($file) : unlink($file);
			}
		}
		
		if ($to === false) rmdir($dir);
	}
    
    
    /*******************************************************************/
	/* 编译开始
	/*******************************************************************/
	
		/* 转换标示符 */
	private function ConverTag($Tag)
	{
		$_count = strlen($Tag);
		$new_array = array('{','}','[',']','$','(',')','*','+','.','?','\\','^','|');
		$Tag_ = '';
		for ($i=0;$i<$_count;$i++)
		{
			$Tag_ .= (in_array($Tag[$i],$new_array)?'\\':'').$Tag[$i];
		}
		return $Tag_;
	}

	/* 模板引擎编译 */
	function Compile($templatename,$clean = 0)
	{
		/* 取得有效模板路径  */
		$templatename =  $this->GetTemplatePath($templatename);
		$compilename =  $this->GetTemplateCompilePath($templatename);
		
		/* 判断模板文件是否存在 */
        !file_exists($templatename) && $this->error('<b>Error:</b>'.$templatename.' no exists.');
		
		/* 取得模板源 */
		$ConverFrom = $this->ReadTemplate($templatename);
        $ConverFrom = trim($ConverFrom);

		/* 如果模板为空,不进行编译 */
		if (!$ConverFrom)
		{
			$this->WriteTemplate($compilename,$ConverFrom);
			return $compilename;
		}

		/* 判断模板缓存文件是否需要更新  */
		if (!$clean)
		{
            $_ftime = filemtime($templatename);
            $_stime = file_exists($compilename) ? filemtime($compilename) : 0;
            
            $clean = $_ftime >= $_stime;
		}
            
		if(!$clean && file_exists($compilename))
		{
			return $compilename;
		}
        
		/* 取得左右标示  */
		$_Left = '(?<!!)'.$this->ConverTag($this->leftTag);
		$_Right = '((?<![!]))'.$this->ConverTag($this->rightTag);
		
		preg_match_all('/'.$_Left.'template (([\w|-|\/]{1,})|(\$([_a-zA-Z][\w]*)))'.$_Right.'/',$ConverFrom,$Include_);
		$Include_count = count($Include_[0]);
		
			/* 模板文件嵌套调用处理  */
		for ($i=0;$i< $Include_count;$i++)
		{
			/* 编译相应调入模板文件 */
			$ConverFrom = str_replace($Include_[0][$i],$this->leftTag.'eval include $this->Compile("'.$Include_[1][$i].'")'.$this->rightTag,$ConverFrom);
		}
		unset($Include_);
			
		 /* 获取模板所使用变量 */
		preg_match_all('/\$([_a-zA-Z][\w]*)/',$ConverFrom,$Global_var);

		if (is_array($Global_var[1]))
		{	
			$Global_var[1] = array_unique($Global_var[1]);

			$Global_var_Im = array('this','_GET','_POST','_COOKIE','_SERVER','_SESSION','_FILES','_ENV');
			$Global_var_out = '';

			foreach ($Global_var[1] as $val)
			{
				if (!in_array($val,$Global_var_Im))
				{
					$Global_var_out .= '$'.$val.' =& $this->GetValue(\''.$val.'\'); ';

				}
			}
		}

		 /* 相关标签转换 */
		$Template_preg = array();
		$Template_Replace = array();

		 /* 判断是否允许插入PHP  */
		if ($this->phpOff === false)
		{
			$arrPreg[] = '/<\?(=|php|)(.+?)\?>/is';
			
			$arrReplace[] = '&lt;?\\1\\2?&gt;';
		}

			/*
			此类编译的语法
			_if 
			_elseif
			_else
			_for
			_while
			_foreach
			_eval
			_echo
			_print_r
			_变量输出
			*/ 
			
		$arrPreg[] = '/'.$_Left.'(else if|elseif) (.*?)'.$_Right.'/i'; 
		$arrPreg[] = '/'.$_Left.'for (.*?)'.$_Right.'/i'; 
		$arrPreg[] = '/'.$_Left.'while (.*?)'.$_Right.'/i'; 
		$arrPreg[] = '/'.$_Left.'(loop|foreach) (.*?)'.$_Right.'/i'; 
		$arrPreg[] = '/'.$_Left.'if (.*?)'.$_Right.'/i'; 
		$arrPreg[] = '/'.$_Left.'else'.$_Right.'/i'; 
		$arrPreg[] = '/'.$_Left."(eval|_)( |[\r\n])(.*?)".$_Right.'/is';
		$arrPreg[] = '/'.$_Left.'_e (.*?)'.$_Right.'/is';
		$arrPreg[] = '/'.$_Left.'_p (.*?)'.$_Right.'/i';
		$arrPreg[] = '/'.$_Left.'\/(if|for|loop|foreach|eval|while)'.$_Right.'/i'; 
		$arrPreg[] = '/'.$_Left.'((( *(\+\+|--) *)*?(([_a-zA-Z][\w]*\(.*?\))|\$((\w+)((\[|\()(\'|")?\$*\w*(\'|")?(\)|\]))*((->)?\$?(\w*)(\((\'|")?(.*?)(\'|")?\)|))){0,})( *\.?[^ \.]*? *)*?){1,})'.$_Right.'/i'; 
		$arrPreg[] = '/'.$_Left.'([A-Z_]*)'.$_Right.'/'; 
        $arrPreg[] = "/(	| ){0,}(\r\n){1,}\";/"; 
		$arrPreg[] = '/'.$_Left.'(\#|\*)(.*?)(\#|\*)'.$_Right.'/';


        /* 编译为相应的PHP文件语法 _所产生错误在运行时提示  */
		$arrReplace[] = '<?php }else if (\\2){ ?>';
		$arrReplace[] = '<?php for (\\1) { ?>';
		$arrReplace[] = '<?php while (\\1) { ?>';
		$arrReplace[] = '<?php foreach ((array)\\2) { $__i++; ?>';
		$arrReplace[] = '<?php if (\\1){ ?>';
		$arrReplace[] = '<?php }else{ ?>';
		$arrReplace[] = '<?php \\3; ?>';
		$arrReplace[] = '<?php echo \\1; ?>';
		$arrReplace[] = '<?php print_r(\\1); ?>';
		$arrReplace[] = '<?php } ?>';
		$arrReplace[] = '<?php echo \\1;?>';
        $arrReplace[] = '<?php echo constant("\\1");?>';
		$arrReplace[] = '';
		$arrReplace[] = '';
				
			/* 执行正则分析编译 */
		$ConverFrom = preg_replace($arrPreg,$arrReplace,$ConverFrom);
		
			/* 过滤敏感字符 */ 
		$ConverFrom = str_replace(array('!'.$this->rightTag,'!'.$this->leftTag,'?><?php'),array($this->rightTag,$this->leftTag,''),$ConverFrom);

			/* 整理输出缓存内容  */
        $Global_var_out && $ConverFrom = "<?php $Global_var_out ?>\r\n".$ConverFrom;
		
		$this->WriteTemplate($compilename,$ConverFrom);
		
		return $compilename;
	}    

	/* 输出模板 */
	function display($templatename,$clean = 0)
	{
		$this->out($templatename,$clean,'display');
	}
	
	/* 返回输出模板 */
	function fetch($templatename,$clean = 0)
	{
		return $this->out($templatename,$clean,'');
	}
	
	/* 输出模板 */
	function out($templatename,$clean = 0,$display = '')
	{
		
		/* 定义错误信息 */
		if ($this->debug)
		{
			error_reporting(E_ERROR | E_WARNING | E_PARSE);
		}
		
		$compilename = $this->Compile($templatename,$clean);    //  重新编译模板
        
        ob_start();
        
        /* 包含编译后模板   */
		include($compilename);
        
        $content = ob_get_contents();
        ob_end_clean();
		
        if(!$display)
        {
            return $content;
        }
        
        echo $content;
	}
    
    function error($msg = '')
    {
        /* 获取最后一次错误记录 */
		$_error = error_get_last();
        $_err = array();
        
        /* 判断是否重要错误信息 */
        switch ($_error['type'])
        {
        	case 1: $_err['type'] = $this->_error[3];break;
        	case 2: $_err['type'] = $this->_error[4];break;
        	case 4: $_err['type'] = $this->_error[5];break;
            default:$_err['type'] = 0;
        }
        
        /* 错误信息格式化 */
        if ($_err['type'])
        {
        	$_err['body'] = is_file($_error['file']) ? file($_error['file']) : array();
        	
        	$_err['err_'] = "<meta http-equiv=Content-Type content=\"text/html;charset=utf-8\"><span style='color:#333;font-size:14px;'>\r\n";
        	$_err['err_'] .= "[Template Error] <br /><br />\r\n";
        	$_err['err_'] .= $this->_error[6].': '.basename($_error['file'])."<br />\r\n";
        	$_err['err_'] .= $this->_error[7].': '.$_err['type']."<br />\r\n";
        	$_err['err_'] .= $this->_error[8].': '.htmlspecialchars($_err['body'][$_error['line']-1])."<br />\r\n";
        	$_err['err_'] .= $this->_error[9].': '.$_error['message']."<br /><br />\r\n";
        	$_err['err_'] .= "\r\n";
    
        	$_err['err_'] .= $this->_error[10].': '.$this->templateCompile.'/'.basename($_error['file'])."<br />\r\n";
        	$_err['err_'] .= $this->_error[11].': '.$_error['line']." {$this->_error[12]}<br />\r\n";
        	$_err['err_'] .= "</span>\r\n";
        }
        else
        {
            $_err['err_'] .= "<meta http-equiv=Content-Type content=\"text/html;charset=utf-8\"><span style='color:#333;font-size:14px;'>".$msg.'</span>';
        }
		
        echo $_err['err_'];
        exit();
    }
}

?>